package tuple.permission;

import soot.*;
import soot.jimple.infoflow.data.SootMethodAndClass;
import soot.jimple.infoflow.util.SootMethodRepresentationParser;

import javax.annotation.Nonnull;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class PermissionRule {
    private static HashSet<String> PERMISSION_STRINGS;
    private static HashSet<SootMethod> PERMISSION_METHODS;

    private static final Pattern full_permission_ptn = Pattern.compile("(android.permission.[A-Za-z_]+)");
    private static String pathToPermissionStrings =
            "C:\\Users\\Yalin Feng\\Code\\AndroidPrivacy\\Project\\data\\ontology\\PermissionStrings.txt";

    private static String pathToPermissionMethods =
            "C:\\Users\\Yalin Feng\\Code\\AndroidPrivacy\\Project\\data\\ontology\\PermissionMethods.txt";

    private static boolean isLoaded = false;
    private static Hierarchy activeHierarchy;
    private static final String ANDROID_PERMISSION_PREFIX = "android.permission";

    public static void setTwoPaths(String pathToPermissionStrings, String pathToPermissionMethods) {
        if (Files.exists(Paths.get(pathToPermissionStrings))) {
            PermissionRule.pathToPermissionStrings = pathToPermissionStrings;
        }
        if (Files.exists(Paths.get(pathToPermissionStrings))) {
            PermissionRule.pathToPermissionMethods = pathToPermissionMethods;
        }
    }

    private static void loadStringsFromText() {
        try (BufferedReader stringsInput = new BufferedReader(new InputStreamReader(new FileInputStream(pathToPermissionStrings)));
             BufferedReader methodsInput = new BufferedReader(new InputStreamReader(new FileInputStream(pathToPermissionMethods)))

        ) {
            PERMISSION_STRINGS = new HashSet<>();
            PERMISSION_METHODS = new HashSet<>();
            while (stringsInput.ready()) {
                String line = stringsInput.readLine().trim();
                if (!line.contains("#") && !line.isEmpty())
                    PERMISSION_STRINGS.add(line);
            }
            while (methodsInput.ready()) {
                String line = methodsInput.readLine().trim();
                if (!line.contains("#") && !line.isEmpty()) {
                    try {
                        SootMethodAndClass sootMethodAndClass = SootMethodRepresentationParser.v().parseSootMethodString(line);
                        SootMethod method = Scene.v().getMethod(sootMethodAndClass.getSignature());
                        PERMISSION_METHODS.add(method);
                    } catch (Exception e) {
                        continue;
                    } finally {
                        PERMISSION_STRINGS.add(line.substring(1, line.length() - 1));
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void ensureLoaded() throws IOException {
        if (!isLoaded) {
            loadStringsFromText();
            isLoaded = true;
            activeHierarchy = Scene.v().getActiveHierarchy();
        }
    }

    public static Set<String> checkMSIsPermission(@Nonnull String signature) {
        Set<String> ret = new HashSet<>();
        try {
            SootMethodAndClass sootMethodAndClass = SootMethodRepresentationParser.v().parseSootMethodString(signature);
            SootMethod method = Scene.v().getMethod(sootMethodAndClass.getSignature());
            for (SootMethod sm : PERMISSION_METHODS) {
                if (method.getSignature().equals(sm.getSignature())) {
                    ret.add(method.getSignature());
                }
                //ERROR CODE
                if (!method.getDeclaringClass().isInterface() && !sm.getClass().isInterface() &&
                        activeHierarchy.isClassSubclassOf(method.getDeclaringClass(), sm.getDeclaringClass())) {
                    if (method.getName().equals(sm.getName())) {
                        ret.add(method.getSignature());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (String permissionString : PERMISSION_STRINGS) {
            if (signature.contains(permissionString))
                ret.add(signature);
        }
        return ret;
    }

    public static Set<String> checkMethodStmtsHasPermission(@Nonnull Set<Value> noDupValues) {
        Set<String> ret = new HashSet<>();
        for (Value val : noDupValues) {
            String valLowered = val.toString().toLowerCase();
            Set<String> invokeStmtContainsPermission = PERMISSION_STRINGS.stream()
                    .map(String::toLowerCase)
                    .filter(str -> valLowered.contains(str))
                    .collect(Collectors.toSet());
            Matcher matcher = full_permission_ptn.matcher(val.toString());
            if (matcher.find()) {
                for (int i = 0; i < matcher.groupCount(); i++) {
                    ret.add(matcher.group(i));
                }
                ret.remove(ANDROID_PERMISSION_PREFIX);
            }
            if (!invokeStmtContainsPermission.isEmpty()) {
                ret.addAll(invokeStmtContainsPermission);
            }
        }
        return ret;
    }


    public static Set<String> checkMethodPermission(SootMethod sm) {
        Set<String> strings = checkMSIsPermission(sm.getSignature());
        if (!strings.isEmpty())
            return strings;
        try {
            if (sm.hasActiveBody()) {
                Body body = sm.retrieveActiveBody();
                Set<Value> noDupValues = body.getUnits().stream()
                        .flatMap(x -> x.getUseAndDefBoxes().stream())
                        .map(x -> x.getValue())
                        .collect(Collectors.toSet());//JimpleLocal, JSpecialInvokeExpre,JNewExpr,JInstanceFieldRef,ThisRef
//                Set<InvokeExpr> allInvokes = noDupValues
//                        .stream()
//                        .filter(x -> x instanceof InvokeExpr)
//                        .map(x -> ((InvokeExpr) (x)))
//                        .collect(Collectors.toSet());
                strings.addAll(checkMethodStmtsHasPermission(noDupValues));
            }
        } catch (RuntimeException re) {
            re.printStackTrace();
        }
        return strings;
    }


}

